// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#include <nebula/util/utf8.h>
#include <nebula/util/utf8_internal.h>
#include <cstdint>
#include <iterator>
#include <mutex>
#include <stdexcept>
#include <utility>

#include <turbo/utility/status.h>
#include <turbo/log/logging.h>
#include <vamos/vamos.h>
// Can be defined by utfcpp
#ifdef NOEXCEPT
#undef NOEXCEPT
#endif

namespace nebula::util {
    namespace internal {

        // Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
        // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.

        // clang-format off
        const uint8_t utf8_small_table[] = { // NOLINT
                // The first part of the table maps bytes to character classes that
                // to reduce the size of the transition table and create bitmasks.
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0,  // NOLINT
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0,  // NOLINT
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0,  // NOLINT
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0,  // NOLINT
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
                9,  // NOLINT
                7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
                7,  // NOLINT
                8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                2,  // NOLINT
                10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
                8,  // NOLINT

                // The second part is a transition table that maps a combination
                // of a state of the automaton and a character class to a state.
                // Character classes are between 0 and 11, states are multiples of 12.
                0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
                12,  // NOLINT
                12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12,
                12,  // NOLINT
                12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12,
                12,  // NOLINT
                12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12,
                12,  // NOLINT
                12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,  // NOLINT
        };
        // clang-format on

        uint16_t utf8_large_table[9 * 256] = {0xffff};

        const uint8_t utf8_byte_size_table[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 4};

        static void InitializeLargeTable() {
            for (uint32_t state = 0; state < 9; ++state) {
                for (uint32_t byte = 0; byte < 256; ++byte) {
                    uint32_t byte_class = utf8_small_table[byte];
                    uint8_t next_state = utf8_small_table[256 + state * 12 + byte_class] / 12;
                    DKCHECK_LT(next_state, 9);
                    utf8_large_table[state * 256 + byte] = static_cast<uint16_t>(next_state * 256);
                }
            }
        }

        TURBO_EXPORT void CheckUTF8Initialized() {
            DKCHECK_EQ(utf8_large_table[0], 0)
                                << "InitializeUTF8() must be called before calling UTF8 routines";
        }

    }  // namespace internal

    static std::once_flag utf8_initialized;

    void InitializeUTF8() {
        std::call_once(utf8_initialized, internal::InitializeLargeTable);
    }

    bool validate_utf8(const uint8_t *data, int64_t size) {
        return vamos::validate_utf8(reinterpret_cast<const char *>(data), size);
    }

    bool validate_utf8(std::string_view str) {
        return vamos::validate_utf8(str);
    }

    static const uint8_t kBOM[] = {0xEF, 0xBB, 0xBF};

    turbo::Result<const uint8_t *> SkipUTF8BOM(const uint8_t *data, int64_t size) {
        int64_t i;
        for (i = 0; i < static_cast<int64_t>(sizeof(kBOM)); ++i) {
            if (size == 0) {
                if (i == 0) {
                    // Empty string
                    return data;
                } else {
                    return turbo::invalid_argument_error("UTF8 string too short (truncated byte order mark?)");
                }
            }
            if (data[i] != kBOM[i]) {
                // BOM not found
                return data;
            }
            --size;
        }
        // BOM found
        return data + i;
    }

    turbo::Result<std::wstring> UTF8ToWideString(std::string_view source) {
        std::wstring result;
        auto rs = vamos::convert_utf8_to_utf32_with_errors(source, &result);
        if (rs.is_err()) {
            return turbo::invalid_argument_error(rs.error);
        }
        return result;
    }

    TURBO_EXPORT turbo::Result<std::string> WideStringToUTF8(const std::wstring &source) {
        std::string result;
        auto rs = vamos::convert_utf32_to_utf8_with_errors(source, &result);
        if (rs.is_err()) {
            return turbo::invalid_argument_error(rs.error);
        }
        return result;
    }

    turbo::Result<std::string> UTF16StringToUTF8(std::u16string_view source) {
        std::string result;
        auto rs = vamos::convert_utf16_to_utf8_with_errors(source, &result);
        if (rs.is_err()) {
            return turbo::invalid_argument_error(rs.error);
        }
        return result;
    }

    turbo::Result<std::u16string> UTF8StringToUTF16(std::string_view source) {
        std::u16string result;
        auto rs = vamos::convert_utf8_to_utf16_with_errors(source, &result);
        if (rs.is_err()) {
            return turbo::invalid_argument_error(rs.error);
        }
        return result;
    }

}  // namespace nebula::util
